<?php
/**
 * Open接口调用
 * User: dxk <1024114660@qq.com>
 * Date: 2022/4/7
 */
namespace App\Service\OpenApi\AdminApi;

use JetBrains\PhpStorm\NoReturn;

class AdminOpenClient
{
    const APP_VERSION = "1.0";
    public $gateway_url = ''; // 请求基本路径
    protected $app_key = ''; // app_key
    protected $app_secret = ''; // app_secret
    protected $token = ''; // token
    protected $timeout = 120; // 设置url最长有效秒数，防止url被截取后执行。
    private $request_body = '';
    private $request_url = '';//请求路径
    private $response;
    public $sign_str = '';

    /**
     * 初始化
     * @param string $token token
     * @throws \Exception
     */
    public function __construct(string $token = '')
    {
        $this->app_key = 'admin';
        $app_secret = config('openapi.' . $this->app_key);
        if (empty($app_secret)) {
            throw new \Exception('不存在的应用');
        }
        $this->app_secret = $app_secret;
        $this->token = $token;
        if (empty($this->gateway_url)) {
            $this->gateway_url = easy_url('openapi/admin');
        }
    }

    /**
     * 获取请求路径
     * @return string
     */
    public function getRequestUrl(): string
    {
        return $this->request_url;
    }

    /**
     * 获取请求参数
     * @return string
     */
    public function getRequestBody(): string
    {
        return $this->request_body;
    }

    #[NoReturn] public function getApiDoc()
    {
        echo $this->request_body . '<br/><br/>';
        echo $this->sign_str . '<br/><br/>';
        echo $this->response;
        easy_p(json_decode($this->response, true, JSON_UNESCAPED_UNICODE));
    }

    /**
     * 生成签名
     * 签名中不包含文件类型的参数，即@前缀
     *
     * @param $params array 参数
     * @return string 签名
     */
    protected function generateSign(array $params): string
    {
        $params = easy_get_field($params, ['method', 'app_key', 'app_version', 'timestamp', 'timeout', 'noncestr', 'param_json', 'token']);
        ksort($params);
        $stringToBeSigned = $this->app_secret;
        foreach ($params as $k => $v) {
            if (!str_starts_with($v, "@")) {
                $stringToBeSigned .= "$k$v";
            }
        }
        unset($k, $v);
        $stringToBeSigned .= $this->app_secret;
        $this->sign_str = $stringToBeSigned;
        return strtoupper(md5($stringToBeSigned));
    }

    /**
     * 产生随机字符串，不长于32位
     *
     * @param int $length
     * @return string
     */
    public static function getNonceStr(int $length = 32): string
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 批量调用之添加请求
     * @param $method
     * @param array $request
     * @return array
     */
    public function addRequest($method, array $request = []): array
    {
        $method = trim($method);
        if (!is_array($request) || empty($method)) {
            return alert_info(500, '参数异常,无法发起请求');
        }
        $sys_params = [
            'method' => $method,
            'app_key' => $this->app_key,
            'token' => $this->token,
            'app_version' => self::APP_VERSION,
            'timestamp' => time(),
            'timeout' => $this->timeout,
            'noncestr' => self::getNonceStr()
        ];
        $request = array_merge($request, $sys_params);
        // 签名
        $request["sign"] = $this->generateSign($request);
        return $request;
    }

    /**
     * 发送批量请求
     * @param array $requests
     * @return array
     */
    public function batchExecute(array $requests): array
    {
        if (empty($requests)) {
            return alert_info(500, '参数异常,无法发起请求');
        }
        // 发起请求
        $requests = json_encode($requests);
        try {
            $resp = $this->curl($this->gateway_url . '/batch', $requests);
            echo $resp;
        } catch (\Exception $e) {
            return ['code' => $e->getCode(), 'msg' => $e->getMessage()];
        }
        $respObject = json_decode($resp, true);
        if (null === $respObject) {
            $respObject = alert_info(500, $resp);
        }
        return $respObject;
    }

    /**
     * 单个请求
     * @param string $method 业务方法
     * @param array $request 请求参数
     * @return array
     */
    public function execute(string $method, array $request = []): array
    {
        $method = trim($method);
        if (!is_array($request) || empty($method)) {
            return alert_info(500, '参数异常,无法发起请求');
        }
        $params = [
            'method' => $method,
            'app_key' => $this->app_key,
            'token' => $this->token,
            'app_version' => self::APP_VERSION,
            'timestamp' => time(),
            'timeout' => $this->timeout,
            'noncestr' => self::getNonceStr(),
            'param_json' => json_encode($request, JSON_UNESCAPED_UNICODE)
        ];
        // 签名
        $params["sign"] = $this->generateSign($params);
        $params = json_encode($params, JSON_UNESCAPED_UNICODE);
        // 发起请求
        try {
            $resp = $this->curl($this->gateway_url, $params);
            $this->response = $resp;
        } catch (\Exception $e) {
            return ['code' => $e->getCode(), 'msg' => $e->getMessage()];
        }
        $respObject = json_decode($resp, true);
        if (null === $respObject) {
            $respObject = alert_info(500, $resp);
        }
        return $respObject;
    }

    /**
     * 发送请求
     * @param string $url
     * @param string $data
     * @return bool|string
     * @throws \Exception
     */
    private function curl(string $url, string $data): bool|string
    {
        $this->request_url = $url;
        $this->request_body = $data;
        $header = ['Content-type:application/json'];
        $ch = curl_init(); //初始化curl
        curl_setopt($ch, CURLOPT_URL, $url);//设置链接
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//设置是否返回信息
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//设置HTTP头
        curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 设置cURL允许执行的最长秒数。
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // 在发起连接前等待的时间，如果设置为0，则无限等待
        curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_POST, 1);//设置为POST方式
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//POST数据
        $reponse = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new \Exception(curl_error($ch), 500);
        } else {
            $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (200 !== $httpStatusCode) {
                throw new \Exception($reponse, $httpStatusCode);
            }
        }
        curl_close($ch);
        return $reponse;
    }
}
